<!--
    by: yuanbiao 2020年10月17日
    name: x-image-viewer
    notes:
-->
<template>
  <div :class="[prefixCls, `${prefixCls}-${size}`]"
    @mousewheel.prevent.stop
  >
    <div ref="viewport" :class="`${prefixCls}__scroll`" :style="scrollStyle">
      <div ref="imagewrapper"
        :class="`${prefixCls}__content`"
        :style="contentStyle"
        v-hammer:pan="hammerHandler"
        v-hammer:panstart="hammerHandler"
        v-hammer:panmove="hammerHandler"
        v-hammer:panend="hammerHandler"
        v-hammer:pinch="hammerHandler"
        v-hammer:pinchstart="hammerHandler"
        v-hammer:pinchmove="hammerHandler"
        v-hammer:pinchend="hammerHandler"
        @mousewheel.prevent.stop="mousewheelHandler"
      >
        <img :class="`${prefixCls}__image`" :src="imageUrl" :draggable="false" :style="imageStyle">
        <img :class="`${prefixCls}__image_copy`" :src="imageUrl" :draggable="false"
          ref="image" :style="imageCopyStyle"
          @error="$event.target.classList.add('error')"
        >
      </div>
    </div>
    <div :class="`${prefixCls}__toolsbar`">
      <div :class="`${prefixCls}__toolsbar_item`" v-for="(v, i) in toolsbarList" :key="i">
        <span :class="`${prefixCls}__toolsbar_btn`" @click="toolsbarItemClick(v.name)">
          <i :class="v.icon"></i>
        </span>
      </div>
    </div>
  </div>
</template>

<script>
const prefixCls = 'x-image-viewer'
export default {
  name: 'x-image-viewer',
  props: {
    size: {
      type: String,
      default: 'default' // default|large|small
    },
    options: {
      type: Object,
      default () {
        return {}
      }
    }
  },
  data() {
    return {
      prefixCls: prefixCls,
      toolsbarList: [
        {name: 'zoomin', label: '放大', icon: 'xvu-iconfont xvu-zoomin'},
        {name: 'zoomout', label: '缩小', icon: 'xvu-iconfont xvu-zoomout'},
        {name: 'restore', label: '恢复', icon: 'xvu-iconfont xvu-restore'}
      ],
      width: 'auto',
      height: 'auto',
      factor: 1.2,
      imageUrl: '',
      imageData: {
        inited: false,
        contain: false,
        // 视口信息
        vp_w: 0, vp_h: 0,
        vp_x: 0, vp_y: 0,
        vp_center_x: 0, vp_center_y: 0,
        // 图片信息
        img_w: 0, img_h: 0,
        imgcopy_w: '100%', imgcopy_h: '100%',
        x: 0, y: 0,
        x_: 0, y_: 0,
        x0: 0, y0: 0,
        x1: 0, y1: 0,
        scale_x: 0, scale_y: 0,
        scale: 1, scale_: 1
      }
    }
  },
  computed: {
    scrollStyle () {
      let width = this.formatSize(this.width)
      let height = this.formatSize(this.height)
      let overflow = this.imageData.contain ? 'hidden' : 'auto'
      return { width, height, overflow }
    },
    contentStyle () {
      let result = {}
      if (this.imageData.contain) {
        result = {
          ...this.scrollStyle
        }
      }
      return result
    },
    imageStyle () {
      let result = {}
      if (this.imageData.contain) {
        result['display'] = 'block'
        result['width'] = '100%'
        result['height'] = '100%'
        result['object-fit'] = 'contain'
      }
      return result
    },
    imageCopyStyle () {
      let result = {
        left: `${this.imageData.x}px`,
        top: `${this.imageData.y}px`
      }
      if (this.imageData.contain) {
        result['object-fit'] = 'contain'
      }
      if (this.imageData.inited) {
        result['width'] = this.formatSize(this.imageData.imgcopy_w)
        result['height'] = this.formatSize(this.imageData.imgcopy_h)
        result['transform-origin'] = `${this.imageData.scale_x} ${this.imageData.scale_y}`
        result['transform'] = `scale(${this.imageData.scale})`
      }
      return result
    }
  },
  watch: {
    options: {
      handler (n, o) {
        console.log(n, o)
        let whiteNames = [ 'width', 'height', 'imageUrl' ]
        Object.keys(n).forEach(k => {
          if (whiteNames.includes(k)) this[k] = n[k]
          if (k === 'imageUrl') {
            let _imageData = {
              inited: false,
              x: 0, y: 0,
              x_: 0, y_: 0,
              x0: 0, y0: 0,
              x1: 0, y1: 0,
              scale_x: 0, scale_y: 0,
              scale: 1, scale_: 1
            }
            for (let k in _imageData) {
              this.imageData[k] = _imageData[k]
            }
          } else if (k === 'contain') {
            this.$set(this.imageData, k, n[k])
          } else if (k === 'innerWidth') {
            this.$set(this.imageData, 'imgcopy_w', this.formatSize(n[k]))
          } else if (k === 'innerHeight') {
            this.$set(this.imageData, 'imgcopy_h', this.formatSize(n[k]))
          }
        })
      },
      deep: true,
      immediate: true
    }
  },
  methods: {
    hammerHandler (event) {
      if (!this.$refs.image.complete) return
      if (!this.imageData.inited) {
        this.$set(this.imageData, 'inited', true)
        this.initData()
      }
      let x = 0
      let y = 0
      switch (event.type) {
        case 'panstart':
          this.$set(this.imageData, 'x0', event.center.x)
          this.$set(this.imageData, 'y0', event.center.y)
          this.imageData.x_ = this.imageData.x
          this.imageData.y_ = this.imageData.y
          this.imageData.x1 = this.imageData.x0 = event.center.x
          this.imageData.y1 = this.imageData.y0 = event.center.y
          break
        case 'panmove':
        case 'panleft':
        case 'panright':
        case 'panup':
        case 'pandown':
          this.imageData.x1 = event.center.x
          this.imageData.y1 = event.center.y
          x = this.imageData.x_ + (this.imageData.x1 - this.imageData.x0)
          y = this.imageData.y_ + (this.imageData.y1 - this.imageData.y0)
          this.$set(this.imageData, 'x', x)
          this.$set(this.imageData, 'y', y)
          break
        case 'pinchstart':
          this.scaleStartHandler({x: event.center.x, y: event.center.y})
          break
        case 'pinchmove':
        case 'pinchleft':
        case 'pinchright':
        case 'pinchup':
        case 'pinchdown':
          this.$set(this.imageData, 'scale', Math.max(this.imageData.scale_ * event.scale, 0.05))
          break
        case 'pinchend':
          this.$nextTick(() => {
            this.scaleEndHandler()
          })
          break
      }
    },
    mousewheelHandler (event) {
      if (!this.$refs.image.complete) return
      if (!this.imageData.inited) {
        this.$set(this.imageData, 'inited', true)
        this.initData()
      }
      let factor = (event.wheelDeltaY > 0 ? this.factor : (1 / this.factor))
      this.scaleStartHandler({x: event.x, y: event.y})
      this.$set(this.imageData, 'scale', Math.max(this.imageData.scale_ * factor, 0.05))
      this.$nextTick(() => {
        this.scaleEndHandler()
      })
    },
    toolsbarItemClick (action) {
      if (!this.$refs.image.complete) return
      if (!this.imageData.inited) {
        this.$set(this.imageData, 'inited', true)
        this.initData()
      }
      if (action === 'zoomin') {
        this.scaleStartHandler({x: this.imageData['vp_center_x'], y: this.imageData['vp_center_y']})
        this.$set(this.imageData, 'scale', this.imageData.scale * this.factor)
        this.$nextTick(() => {
          this.scaleEndHandler()
        })
      }
      else if (action === 'zoomout') {
        this.scaleStartHandler({x: this.imageData['vp_center_x'], y: this.imageData['vp_center_y']})
        this.$set(this.imageData, 'scale', this.imageData.scale / this.factor)
        this.$nextTick(() => {
          this.scaleEndHandler()
        })
      }
      else if (action === 'restore') {
        this.imageData = {
          ...this.imageData,
          x: 0, y: 0,  x_: 0, y_: 0,
          x0: 0, y0: 0, x1: 0, y1: 0,
          imgcopy_w: this.imageData.img_w,
          imgcopy_h: this.imageData.img_h,
          scale: 1, scale_: 1
        }
      }
    },
    scaleStartHandler ({x, y}) {
      if (this.$refs.image) {
        let imgInfo = this.$refs.image.getBoundingClientRect()
        let scale_x = x - imgInfo.x
        let scale_y = y - imgInfo.y
        this.imageData.scale_x = this.px2per(scale_x, imgInfo.width)
        this.imageData.scale_y = this.px2per(scale_y, imgInfo.height)
        this.imageData.scale_ = this.imageData.scale
      }
    },
    scaleEndHandler () {
      // 计算图片大小及位置
      if (this.$refs.image && this.$refs.imagewrapper) {
        let imgInfo = this.$refs.image.getBoundingClientRect()
        let imgWrapperInfo = this.$refs.imagewrapper.getBoundingClientRect()
        this.$set(this.imageData, 'x', imgInfo.x - imgWrapperInfo.x)
        this.$set(this.imageData, 'y', imgInfo.y - imgWrapperInfo.y)
        this.$set(this.imageData, 'imgcopy_w', imgInfo.width)
        this.$set(this.imageData, 'imgcopy_h', imgInfo.height)
        this.$set(this.imageData, 'scale_', 1)
        this.$set(this.imageData, 'scale', 1)
      }
    },
    px2per (px, total) {
      let result = 0
      px = parseFloat(px)
      if (!isNaN(px)) {
        result = (px / total || 0) * 100
      }
      return `${result}%`
    },
    formatSize (val) {
      return /^[0-9]+([.]{1}[0-9]+)?$/.test(val) ? `${val}px` : val
    },
    initData () {
      // 计算视口大小&内容大小&坐标原点
      if (this.$refs.viewport) {
        let vpInfo = this.$refs.viewport.getBoundingClientRect()
        this.imageData['vp_w'] = vpInfo.width
        this.imageData['vp_h'] = vpInfo.height
        this.imageData['vp_x'] = vpInfo.x
        this.imageData['vp_y'] = vpInfo.y
        this.imageData['vp_center_x'] = vpInfo.x + vpInfo.width / 2
        this.imageData['vp_center_y'] = vpInfo.y + vpInfo.height / 2
      }
      // 计算图片大小及位置
      if (this.$refs.image) {
        let imgInfo = this.$refs.image.getBoundingClientRect()
        this.imageData['img_w'] = this.imageData['imgcopy_w'] = imgInfo.width
        this.imageData['img_h'] = this.imageData['imgcopy_h'] = imgInfo.height
        this.imageData['x'] = 0
        this.imageData['y'] = 0
        this.scaleStartHandler({x: this.imageData['vp_center_x'], y: this.imageData['vp_center_y']})
      }
    }
  }
}
</script>
